Skip to content

feat(m5): deferred-items sweep — pre-accept WS auth, vault envelopes, idempotent write retries, honest RPC taxonomy#6

Merged
gcharang merged 2 commits into
masterfrom
m5-deferred-fixes
Jul 16, 2026
Merged

feat(m5): deferred-items sweep — pre-accept WS auth, vault envelopes, idempotent write retries, honest RPC taxonomy#6
gcharang merged 2 commits into
masterfrom
m5-deferred-fixes

Conversation

@gcharang

Copy link
Copy Markdown
Contributor

Closes out every deliberately-deferred M5 item plus the known cosmetic/noise quirks, and performs understudy's first real deploy. 184 tests green across the workspace (protocol 21 / extension 49 / connector 24 / backend 90); zero unhandled-rejection lines.

What changed

Security & correctness

  • Pre-accept WS/HTTP authrouteAgentRequest's onBeforeConnect/onBeforeRequest reject unauthorized upgrades at the Worker edge (401 bad token / 404 cross-tenant, no existence oracle) before the DO accepts them. The in-DO onConnect gate stays as defense in depth.
  • onClose auth-gating — a never-authorized socket's close can no longer stamp a pending session detached.
  • Honest RPC taxonomy, no noisedispatch/fillSecret return a typed DispatchOutcome instead of throwing across the DO RPC boundary (workerd logs every server-side RPC rejection as Uncaught (in promise) even when handled). Route mapping: 503 not-connected / 503 resynced-mid-command / 504 timeout / 409 duplicate-in-flight; genuine bugs still 500 (with an assertNever exhaustiveness backstop).
  • Vault envelope encryption — KV holds only v1.<iv>.<ct> AES-256-GCM envelopes under a VAULT_MASTER_KEY Worker secret (src/vault.ts); scripts/vault-put.mjs seeds them; a legacy/corrupt at-rest value fails closed with a scrubbed ok:false.

Idempotent write retries (end to end)

  • @understudy/protocol exports WRITE_COMMAND_TYPES as the single classification source and reclassifies scroll/switch_tab as writes — they're user-visible side effects, so a dry-run now simulates them (previously a dry-run switch_tab actually switched the tab) and a retry replays them (a relative-dy scroll no longer double-scrolls). This collapses the old connector⇄protocol divergence.
  • The connector derives the wire commandId from the breakwater idempotency key (ik_<key>); the service caches completed write Events (completedWrites, cap 100) and replays repeats (409 for a concurrent duplicate); the extension keeps a matching cap-100 replay and in-flight record so a duplicate that arrives while the original is still executing is dropped, not re-run — closing a silent-double-execution race under a backend-timeout.

Probes, packaging, CI, deploy

  • OOPIF probe in apps/cdp-spike (Target.setAutoAttach{flatten:true} + session-scoped commands + a bundled cross-origin-iframe page + runbook) — retires the M0-deferred unknown when run (attended, Chrome ≥125).
  • CHANGELOG in both packages' files; changeset (protocol + connector minor); engines asymmetry documented.
  • Actions bumped to node24 runtimes (checkout@v5, setup-node@v5, pnpm/action-setup@v6); peerDependencyRules in pnpm-workspace.yaml for the workers-types-5 / ai-sdk-zod-4 peer warnings.
  • First real deploy to https://understudy-backend.gcharang.workers.dev with real minted secrets + the VAULT namespace; live smoke verified (health, 401, mint, fail-fast 503, WS-gate 401, encrypted vault seed). Runbook in apps/backend/README.md.

Quality gate

Three parallel review lanes (clean-code / architecture / QA) ran on the branch. They caught two real blocking bugs the initial 173-test suite missed — silent double-execution under a timeout race, and the scroll/switch_tab dry-run/replay gap — both fixed at root and folded into this commit; a re-review round signed both off, and one further non-blocking straggler-record race (session change mid-execution) was closed with a remember() in-flight guard.

🤖 Generated with Claude Code

gcharang and others added 2 commits July 17, 2026 02:31
… idempotent write retries, honest RPC taxonomy

Closes out every deliberately-deferred item plus the known cosmetic/noise
quirks, and performs the first real deploy:

- backend: routeAgentRequest onBeforeConnect/onBeforeRequest reject
  unauthorized upgrades 401/404 at the Worker edge (in-DO onConnect gate
  kept as defense in depth); onClose only stamps status for connections
  that were authorized (a rejected socket can no longer mark a pending
  session detached)
- backend: dispatch/fillSecret return a typed DispatchOutcome instead of
  throwing across the DO RPC boundary — kills workerd's
  'Uncaught (in promise)' instrumentation noise at the root and upgrades
  the taxonomy (503 not-connected, 503 resynced-mid-command, 504 timeout,
  409 duplicate-in-flight; unknown errors still 500)
- vault: AES-256-GCM envelope encryption over KV (v1.<iv>.<ct>, fresh IV)
  under a VAULT_MASTER_KEY Worker secret; EncryptedKvVault behind the
  VaultBinding seam; scripts/vault-put.mjs seeds envelopes (plaintext via
  stdin); legacy plaintext values fail closed
- idempotent write retries end to end: protocol exports
  WRITE_COMMAND_TYPES (single classification source, connector's act union
  pinned to it at compile time); connector derives write commandIds from
  the breakwater idempotency key (ik_<key>); the session DO records
  completed write Events (cap 100) and replays repeats; the extension
  keeps its own replay record (cap 50, storage.session) for the
  service-timed-out-after-execution case
- cdp-spike: attended OOPIF probe (Target.setAutoAttach{flatten:true} +
  session-scoped commands) with bundled cross-origin-iframe test page and
  runbook — retires the M0-deferred unknown when run
- packaging/CI: CHANGELOG.md in both packages' files arrays; changeset
  (protocol minor, connector minor); engines asymmetry documented;
  actions/checkout@v5, setup-node@v5, pnpm/action-setup@v6 (node24
  runtimes); pnpm peerDependencyRules for workers-types-5 / ai-sdk-zod-4
  peer noise
- deploy: first wrangler deploy to
  https://understudy-backend.gcharang.workers.dev with real minted
  secrets and the VAULT namespace; live smoke verified (health, 401,
  mint, fail-fast 503, WS-gate 401, encrypted vault seed); runbook in
  apps/backend/README.md

173 tests green across the workspace; zero unhandled-rejection lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- .gitignore: add .secrets* (operator-local secret backups; the deployed
  worker is canonical). Specific prefix so the tracked .dev.vars.example is
  unaffected.
- README Secrets section: per-secret table (what it is / regenerate / rotation
  impact), when to rotate, and the re-push paths (per-secret put or bulk).
- The actual values live in apps/backend/.secrets.production.env, gitignored
  and never committed — Cloudflare never shows a secret again after put, so
  that file is the only readable backup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gcharang
gcharang merged commit 9e21dae into master Jul 16, 2026
1 check passed
@gcharang
gcharang deleted the m5-deferred-fixes branch July 17, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant